-- model1
-- builder:command(
-- 	keyword("display", "xx"),
-- 	keyword("system", "xx"),
-- 	group(
-- 		keyword("cpu-usage", "xx"),
-- 		parameter("cpuid", "The cpu index.", range{0, 64}),
-- 		or_(),
-- 		keyword("memory-usage", "xxx")
-- 	),
-- 	optional(
-- 		keyword("brief", "Brief information"),
-- 		or_(),
-- 		keyword("verbose", "Detail information")
-- 	),
-- );

-- model2
-- builder:command{
--     elements = {
--         keyword("display", "xxx"),
--         keyword("system", "xxx"),
--         keyword("cpu-usage", "xx"),
--         keyword("memory-usage", "xxx"),
--         keyword("brief", "brief information."),
--         keyword("verbose", "detail information."),
--         parameter("cpuid", "the cpu index.", range{0, 64})
--     },
--     expressions = {
--         "display system { cpu-usage <cpuid> | memory-usage } [ brief | verbose ]"
--     },
--     class = xxx
-- }
--------------------------------------------------------------------------------
local function keyword(name, desc, dynamic_help, dynamic_check)
    return {
        type = 'keyword',
        name = name,
        description = desc,
        dynamic_help = not not dynamic_help,
        dynamic_check = not not dynamic_check
    }
end

local function parameter(name, desc, range, dynamic_help, dynamic_check)
    return {
        type = 'parameter',
        name = name,
        description = desc,
        range = range,
        dynamic_help = not not dynamic_help,
        dynamic_check = not not dynamic_check
    }
end

local function terminate()
    return { type = 'terminate', name = '<cr>' }
end

local function range(min, max)
    return { min = min, max = max }
end

local function tokenize(expr)
    local tokens = {}
    for tok in string.gmatch(expr, '[^%s<>]+') do
        table.insert(tokens, tok)
    end
    return tok
end

--------------------------------------------------------------------------------

local cli_tree = {}

function cli_tree.new(root)
    local tree = { root = root, leaf = root }
    return setmetatable(tree, { __index = cli_tree })
end

function cli_tree.new_node(element)
    return { element = element, children = {} }
end

function cli_tree:add_node(name, node)
    if self.leaf.children[name] == nil then
        self.leaf.children[name] = node
        self.leaf = node
    end
end

function cli_tree:print()
    local function print_tree(root, depth)
        if type(root) ~= 'table' then
            return
        end

        local spaces = string.rep(' ', depth)
        for k, v in pairs(root) do
            print(spaces, k)
            print_tree(v, depth + 2)
        end
    end
    print_tree(self.root, 0)
end
--------------------------------------------------------------------------------

local tree_modifier = {}

function tree_modifier.new(tree, prev_modifier)
    local modifier = { tree = tree, prev_modifier = prev_modifier }
    return setmetatable(modifier, { __index = tree_modifier })
end

function tree_modifier:add_node(node)
    self.tree:add_node(node.element.name, node)
end

function tree_modifier:pop()
    return self.prev_modifier
end

function tree_modifier:reset()
    
end

--------------------------------------------------------------------------------

local function check_config(config)
    assert(config and config.elements and config.expressions and config.class, "invalid param.")
end

local function is_operator_char(c)
    return c == '{' or c == '}' or c == '[' or c == ']' or c == '|'
end

local function process_op(c, ctx)
    local stack = ctx.stack
    if c == '{' then
        table.insert(stack, c)
        ctx.modifier = tree_modifier.new(ctx.modifier.tree, ctx.modifier)
    elseif c == '}' then
        local top = table.remove(stack, #stack)
        assert(top == '{', 'invalid expression, unclosed brace.')
        ctx.modifier = ctx.modifier:pop()
    elseif c == '[' then
        table.insert(stack, c)
        ctx.modifier:add_node(cli_tree.new_node(terminate()))
        ctx.modifier = tree_modifier.new(ctx.modifier.tree, ctx.modifier)
    elseif c == ']' then
        local top = table.remove(stack, #stack)
        assert(top == '[', 'invalid expression, unclosed bracket.')
        ctx.modifier = ctx.modifier:pop()
    end
end

local function parse_expressions(config, cmd_tree)
    local elements_map = {}
    for i, element in ipairs(config.elements) do
        print(i, element.type, element.name)
        elements_map[element.name] = element;
    end

    for _, expr in ipairs(config.expressions) do
        local parse_context = { stack = {}, modifier = tree_modifier.new(cmd_tree) }
        for match in string.gmatch(expr, '[^%s<>]+') do
            assert(match, string.format("invalid command expression '%s'", expr))
            if is_operator_char(match) then
                process_op(match, parse_context)
            else
                assert(elements_map[match], string.format("element not defined for '%s'", match))
                parse_context.modifier:add_node(cli_tree.new_node(elements_map[match]))
            end
        end
    end
end

local builder = {}

function builder.new()
    local obj = { tree = cli_tree.new(cli_tree.new_node()), elements = {} }
    return setmetatable(obj, {__index = builder})
end

function builder:command(config)
    check_config(config)
    parse_expressions(config, self.tree)
    return self.tree
end

local builder_ = builder.new()

local cc = builder_:command{
    elements = {
        keyword("display", "xxx"),
        keyword("system", "xxx"),
        keyword("cpu-usage", "xx"),
        keyword("memory-usage", "xxx"),
        keyword("brief", "brief information."),
        keyword("verbose", "detail information."),
        parameter("cpuid", "the cpu index.", range{0, 64})
    },
    expressions = {
        "display system cpu-usage <cpuid> brief"
    },
    class = "display_system_info"
}

cc:print()
